#include "acq_gpu.h"
#include<algorithm>
#include "pciout.h"
#include "cuda_func.h"
#include <process.h>
#include<algorithm>
#include <iostream>
#include <windows.h>
#include <conio.h>>
#include <fstream>

#include "AlazarApi.h"
#include "AlazarCmd.h"
#include "AlazarDSP.h"
#include "AlazarError.h"
#include "Qimage.h"
#include "../common.h"

#define CHECK(call){const cudaError_t error = call;if (error != cudaSuccess){std::cout <<("Error:%d\n", __LINE__);std::cout <<("code:%d,reason:%s\n", error, cudaGetErrorString(error));exit(1);}}

QImage grayMatToQImage(const cv::Mat& mat)
{
	QImage image(mat.cols, mat.rows, QImage::Format_Indexed8);

	image.setColorCount(256);
	for (int i = 0; i < 256; i++)
	{
		image.setColor(i, qRgb(i, i, i));
	}

	uchar *pSrc = mat.data;
	for (int row = 0; row < mat.rows; row++)
	{
		uchar *pDest = image.scanLine(row);
		memcpy(pDest, pSrc, mat.cols);
		pSrc += mat.step;
	}

	return image;
}

AcquirePro::AcquirePro(Processing *proc)
{
	mpproc = proc;
	systemId = 1;
	boardId = 1;

	deviceCount = 0;
	deviceIndex = 0;
	char deviceName[256];

	ERROR_CHECK(ATS_GPU_QueryCUDADeviceCount(&deviceCount));

	ERROR_CHECK(ATS_GPU_QueryCUDADeviceName(deviceIndex, deviceName, 256));

	m_boardHandle = AlazarGetBoardBySystemID(systemId, boardId);

	if (m_boardHandle == NULL)
	{
		std::cout << "Error: Unable to open board system Id : " << systemId << " board Id: " << boardId << std::endl;
	}

	ERROR_CHECK(ATS_GPU_SetCUDAComputeDevice(m_boardHandle, deviceIndex));

exit:
	{}
}

bool AcquirePro::acquireData()
{
	isAcquire = true;
	if (!isshow)
		emit sig_start_collect();
	else
		emit sig_start_show();

	// TODO: Specifiy the total number of buffers to capture
	U32 buffersPerAcquisition = mpproc->clp.frame_num * mpproc->clp.rep_num;

	if (isshow)
	{
		buffersPerAcquisition = 0XFFFFFFFF;
	}

	U32 channelMask = CHANNEL_A;

	// TODO : set AutoDMA flags for acquisition
	//U32 autoDMAFlags = ADMA_CONTINUOUS_MODE | ADMA_EXTERNAL_STARTCAPTURE;
	U32 autoDMAFlags = ADMA_NPT | ADMA_EXTERNAL_STARTCAPTURE;

	// TODO : set ATS-GPU flags
	U32 atsGPUFlags = ATS_GPU_SETUP_FLAG_UNPACK | ATS_GPU_SETUP_FLAG_DEINTERLEAVE;
	const U32 pack_mode = PACK_12_BITS_PER_SAMPLE;

	// Allocate buffer pointers
	int fftLen = mpproc->cp.A_line_pixal / 2 + 1;
	float *gpuWindow;
	cudaError_t ret = cudaMalloc((void**)&gpuWindow, mpproc->cp.A_line_pixal * sizeof(float));
	compute_hanning_window_gpu(gpuWindow, mpproc->cp.A_line_pixal);

	unsigned short *gpuBufferArray[STREAM_NUM];
	double *gpuPreFFTOut[STREAM_NUM];
	double2 *gpuFFTOut[STREAM_NUM];
	float *gpuPostFFTOutR[STREAM_NUM];
	float *gpuPostFFTOutI[STREAM_NUM];
	float *gpuEnfaceLineOut[STREAM_NUM];
	float *gpuResultBufferArray[STREAM_NUM];
	cufftHandle FFTplan[STREAM_NUM];
	cudaStream_t stream[STREAM_NUM];

	int samples_size[3];
	samples_size[0] = mpproc->clp.frame_num * mpproc->clp.rep_num;
	samples_size[1] = mpproc->clp.line_num;
	samples_size[2] = mpproc->clp.down_depth;
	mpproc->I_3D_R[0] = cv::Mat(3, samples_size, CV_32FC1);
	mpproc->I_3D_I[0] = cv::Mat(3, samples_size, CV_32FC1);
	samples_size[0] = mpproc->clp.frame_num;
	mpproc->img_3D[0] = cv::Mat(3, samples_size, CV_32FC1);

	BOOL success = TRUE;
	// Calculate the number of enabled channels from the channel mask

	long channelsPerBoard;
	ERROR_CHECK(AlazarGetParameter(m_boardHandle, CHANNEL_ALL, GET_CHANNELS_PER_BOARD, &channelsPerBoard));

	// Get the sample size in bits, and the on-board memory size in samples per
	// channel
	{
		U8 bitsPerSample;
		U32 maxSamplesPerChannel;
		ERROR_CHECK(AlazarGetChannelInfo(m_boardHandle, &maxSamplesPerChannel, &bitsPerSample));

		// Calculate the size of each DMA buffer in bytes
		double bytesPerSample = ((bitsPerSample + 7) / 8);
		bitsPerSample = bytesPerSample * 8;

		U32 samplesPerBuffer = mpproc->cp.A_line_pixal * mpproc->clp.line_num;
		U32 bytesPerBuffer = static_cast<U32>(bytesPerSample * samplesPerBuffer);

		// Setup the acquisition
		/*{
			ERROR_CHECK(ATS_GPU_Setup(m_boardHandle, channelMask, 0, samplesPerBuffer, 1, buffersPerAcquisition, autoDMAFlags, atsGPUFlags));
		}*/
		U32 OCTOptions = ATS_GPU_OCT_LOG_OUTPUT;
		U32 fftBytesPerBuffer;
		{
			//ERROR_CHECK(ATS_GPU_Setup(m_boardHandle, channelMask, 0, samplesPerBuffer, 1, buffersPerAcquisition, autoDMAFlags, atsGPUFlags));
			ERROR_CHECK(ATS_GPU_OCT_Setup(m_boardHandle, channelMask, 0, mpproc->cp.A_line_pixal, mpproc->clp.line_num, 0x7FFFFFFF,
				autoDMAFlags, OCTOptions, mpproc->cp.A_line_pixal, NULL, &fftBytesPerBuffer));

		}

		for (int i = 0; i < STREAM_NUM; i++)
		{
			//cudaMalloc(&gpuBufferArray[i], A_line_pixal * m_linenum * sizeof(unsigned short));
			gpuBufferArray[i] = (unsigned short *)ATS_GPU_AllocBuffer(m_boardHandle, mpproc->cp.A_line_pixal * mpproc->clp.line_num * sizeof(unsigned short), &stream[i]);
			// Post this buffer to the ATS board
			ERROR_CHECK(ATS_GPU_PostBuffer(m_boardHandle, gpuBufferArray[i], bytesPerBuffer));

			cudaMalloc(&gpuPreFFTOut[i], mpproc->cp.A_line_pixal * mpproc->clp.line_num * sizeof(double));
			cudaMalloc(&gpuPostFFTOutR[i], mpproc->clp.down_depth * mpproc->clp.line_num * sizeof(float));
			cudaMalloc(&gpuPostFFTOutI[i], mpproc->clp.down_depth * mpproc->clp.line_num * sizeof(float));
			cudaMalloc(&gpuFFTOut[i], fftLen * mpproc->clp.line_num * sizeof(double2));
			cudaMalloc(&gpuResultBufferArray[i], mpproc->clp.down_depth * mpproc->clp.line_num * sizeof(float2));
			cudaMalloc(&gpuEnfaceLineOut[i], mpproc->clp.line_num * sizeof(float));

			//cudaStreamCreate(&stream[i]);
			cufftPlan1d(&FFTplan[i], mpproc->cp.A_line_pixal, CUFFT_D2Z, mpproc->clp.line_num);
			cufftSetStream(FFTplan[i], stream[i]);
		}

		std::list<int> pendingBufferIndexList;

		//开始振镜信号发生
		unsigned int addr;
		HANDLE m_signal_thread = (HANDLE)_beginthreadex(NULL, 0, CreateSignal, this, 0, &addr);

		//开始存储线程
		if (mpproc->cp.needsave && !isshow)
		{
			unsigned int addr1;
			HANDLE m_save_thread = (HANDLE)_beginthreadex(NULL, 0, SaveData, this, 0, &addr1);
		}

		//开始采集
		ERROR_CHECK(ATS_GPU_StartCapture(m_boardHandle));

		if (success)
		{
			//std::cout << ("Capturing %d buffers with GPU processing... press any key to abort\n", buffersPerAcquisition);

			U32 startTickCount = GetTickCount();
			U32 buffersCompleted = 0;
			U32 buffersTransed = 0;
			unsigned short *saveBuff = NULL;
			cv::Mat enfaceMat(cv::Size(mpproc->clp.line_num, mpproc->clp.frame_num), CV_32FC1);

			int processedid = 0;
			while (buffersCompleted < buffersPerAcquisition && isAcquire)
			{
				U32 timeout_ms = 5000;
				U32 bufferIndex = buffersCompleted % STREAM_NUM;
				cudaStream_t cuStream;

				ERROR_CHECK(ATS_GPU_GetBuffer(m_boardHandle, gpuBufferArray[bufferIndex], timeout_ms, &stream[bufferIndex]));


				/*std::ifstream file;
				std::string filedir = "D:/project/HuaweiOCT_pack/HuaweiOCTOneBatch/";
				unsigned short *testbuff = new unsigned short[cp.A_line_pixal * clp.line_num];

				std::string filename = filedir + "1.bin";
				file.open(filename, std::ios::binary);
				file.read((char *)testbuff, 2097152);
				cudaMemcpy(gpuBufferArray[bufferIndex], testbuff, cp.A_line_pixal * clp.line_num * sizeof(unsigned short), cudaMemcpyHostToDevice);*/

				if (success)
				{
					int ret = process_fft_bframe_enface(mpproc->clp.line_num, mpproc->clp.start_z, mpproc->clp.down_depth, gpuWindow, mpproc->cp.A_line_pixal, gpuBufferArray[bufferIndex], gpuResultBufferArray[bufferIndex], gpuPreFFTOut[bufferIndex],
						gpuPostFFTOutR[bufferIndex], gpuPostFFTOutI[bufferIndex], gpuEnfaceLineOut[bufferIndex], gpuFFTOut[bufferIndex], FFTplan[bufferIndex], mpproc->cp.enface_show_low, mpproc->cp.enface_show_high, stream[bufferIndex]);

					pendingBufferIndexList.push_back(bufferIndex);
					if (pendingBufferIndexList.size() > STREAM_NUM - 1)
					{
						ERROR_CHECK(ATS_CUDA_StreamSynchronize(stream[pendingBufferIndexList.front()]));
					}

					// Complete all streams at last buffer acquired
					if (buffersCompleted == buffersPerAcquisition - 1)
					{
						for (std::list<int>::iterator itt = pendingBufferIndexList.begin(); itt != pendingBufferIndexList.end(); ++itt)
						{
							ERROR_CHECK(ATS_CUDA_StreamSynchronize(stream[*itt]));
						}
					}

					buffersCompleted++;

					while (pendingBufferIndexList.size() > 0 && (cudaSuccess == cudaStreamQuery(stream[pendingBufferIndexList.front()])))
					{
						int bufferIndex = pendingBufferIndexList.front();

						//copy memory

						if (!isshow)
						{
							cudaError et1 = cudaMemcpy(mpproc->I_3D_R[0].ptr<float>(processedid), gpuPostFFTOutR[bufferIndex], mpproc->clp.down_depth * mpproc->clp.line_num * sizeof(float), cudaMemcpyDeviceToHost);
							cudaError et2 = cudaMemcpy(mpproc->I_3D_I[0].ptr<float>(processedid), gpuPostFFTOutI[bufferIndex], mpproc->clp.down_depth * mpproc->clp.line_num * sizeof(float), cudaMemcpyDeviceToHost);
						}
						
						if (processedid % mpproc->clp.rep_num == mpproc->clp.rep_num - 1)
						{

							cudaError et3 = cudaMemcpy(mpproc->img_3D[0].ptr<float>(processedid / mpproc->clp.rep_num), gpuResultBufferArray[bufferIndex], mpproc->clp.down_depth * mpproc->clp.line_num * sizeof(float), cudaMemcpyDeviceToHost);
						}

						if (!mpproc->cp.show_enfacetrue && processedid % 10 == 0) // show bframe
						{
							/*double2 *tests = new double2[clp.down_depth * clp.line_num];
							cudaError ettest = cudaMemcpy(tests, gpuPreFFTOut[bufferIndex], clp.down_depth * clp.line_num * sizeof(float2), cudaMemcpyDeviceToHost);*/
							float *showoutbuff = new float[mpproc->clp.down_depth * mpproc->clp.line_num];
							cudaError ett = cudaMemcpy(showoutbuff, gpuResultBufferArray[bufferIndex], mpproc->clp.down_depth * mpproc->clp.line_num * sizeof(float), cudaMemcpyDeviceToHost);
							cv::Mat showimg(cv::Size(mpproc->clp.down_depth, mpproc->clp.line_num), CV_32FC1, showoutbuff);
							cv::transpose(showimg, showimg);
							showimg = 255.0 / (mpproc->cp.scale_max - mpproc->cp.scale_min) * (showimg - mpproc->cp.scale_min);
							showimg.convertTo(showimg, CV_8U);
							QImage qImage = grayMatToQImage(showimg);//QImage(showimg.data, showimg.cols, showimg.rows, showimg.step, QImage::Format_RGB888);
							emit image_ready(qImage);
						}
						
						if (mpproc->cp.show_enfacetrue) //show enface
						{
							if (processedid % mpproc->clp.rep_num == 0)
							{
								float *cpubuf = new float[mpproc->clp.line_num];
								cudaMemcpy(cpubuf, gpuEnfaceLineOut[bufferIndex], mpproc->clp.line_num * sizeof(float), cudaMemcpyDeviceToHost);
								memcpy(enfaceMat.ptr<float>(processedid % (mpproc->clp.rep_num * mpproc->clp.frame_num) / mpproc->clp.rep_num), cpubuf, mpproc->clp.line_num * sizeof(float));
							}
							
							if (processedid % mpproc->clp.frame_num == mpproc->clp.frame_num - 1)
							{
								//cv::Mat showimg = 255.0 / (mpproc->cp.scale_max * 4 - mpproc->cp.scale_min * 4) * (enfaceMat - mpproc->cp.scale_min * 4);
								cv::Mat showimg;
								imadjust_float(enfaceMat, showimg);
								showimg = showimg * 255 * mpproc->cp.light_scale;
								showimg.convertTo(showimg, CV_8U);
								QImage qImage = grayMatToQImage(showimg);//QImage(showimg.data, showimg.cols, showimg.rows, showimg.step, QImage::Format_RGB888);
								emit image_ready(qImage);
							}
						}

						processedid++;
						processedid = processedid % (mpproc->clp.rep_num * mpproc->clp.frame_num);

						if (mpproc->cp.needsave && !isshow)
						{
							if (buffersTransed % mpproc->clp.rep_num == 0)
							{
								saveBuff = new  unsigned short[mpproc->clp.line_num * mpproc->cp.A_line_pixal * mpproc->clp.rep_num];
							}

							cudaMemcpy((void*)&saveBuff[mpproc->clp.line_num * mpproc->cp.A_line_pixal * (buffersTransed % mpproc->clp.rep_num)], (void*)gpuBufferArray[bufferIndex], mpproc->cp.A_line_pixal *mpproc->clp.line_num * sizeof(unsigned short), cudaMemcpyDeviceToHost);

							if (buffersTransed % mpproc->clp.rep_num == mpproc->clp.rep_num - 1)
							{
								listSaveBuffer.push_back(saveBuff);
							}
						}

						pendingBufferIndexList.pop_front();
						buffersTransed++;
					}
					ERROR_CHECK(ATS_GPU_PostBuffer(m_boardHandle, gpuBufferArray[bufferIndex], bytesPerBuffer));
				}

				// If the acquisition failed, exit the acquisition loop
				if (!success)
					break;
			}
		}
	}

exit:
	ATS_GPU_AbortCapture(m_boardHandle);
	pciEnd();
	

	// Free buffers
	for (int i = 0; i < STREAM_NUM; i++)
	{
		ATS_GPU_FreeBuffer(m_boardHandle, gpuBufferArray[i]);
		//cudaFree(gpuBufferArray[i]);
		cudaFree(gpuPreFFTOut[i]);
		cudaFree(gpuFFTOut[i]);
		cudaFree(gpuPostFFTOutR[i]);
		cudaFree(gpuPostFFTOutI[i]);
		cudaFree(gpuResultBufferArray[i]);
		cudaFree(gpuEnfaceLineOut[i]);
		cudaStreamDestroy(stream[i]);
	}
	isAcquire = false;
	emit sig_stop_collect();
	cudaFree(gpuWindow);

	return success;
}

bool AcquirePro::configureBoard()
{
	ERROR_CHECK(
		AlazarSetCaptureClock(m_boardHandle,          // HANDLE -- board handle
			EXTERNAL_CLOCK,       // U32 -- clock source id
			SAMPLE_RATE_USER_DEF, // U32 -- sample rate id 
			//SAMPLE_RATE_500MSPS, //测试
			CLOCK_EDGE_RISING,    // U32 -- clock edge id
			0                     // U32 -- clock decimation
		));

	ERROR_CHECK(AlazarInputControlEx(m_boardHandle, CHANNEL_A, DC_COUPLING,
		INPUT_RANGE_PM_400_MV, IMPEDANCE_50_OHM));

	/*ERROR_CHECK(retCode =
		AlazarInputControlEx(m_boardHandle, CHANNEL_B, DC_COUPLING,
			INPUT_RANGE_PM_400_MV, IMPEDANCE_50_OHM));*/

	ERROR_CHECK(AlazarSetTriggerOperation(
		m_boardHandle,            // HANDLE -- board handle
		TRIG_ENGINE_OP_J,       // U32 -- trigger operation
		TRIG_ENGINE_J,          // U32 -- trigger engine id
		TRIG_EXTERNAL,          // U32 -- trigger source id
		TRIGGER_SLOPE_POSITIVE, // U32 -- trigger slope id
		140,           // U32 -- trigger level from 0 (-range) to 255 (+range)
		TRIG_ENGINE_K, // U32 -- trigger engine id
		TRIG_DISABLE,  // U32 -- trigger source id for engine K
		TRIGGER_SLOPE_POSITIVE, // U32 -- trigger slope id
		140 // U32 -- trigger level from 0 (-range) to 255 (+range)
	));

	ERROR_CHECK(AlazarSetExternalTrigger(
		m_boardHandle, // HANDLE -- board handle
		DC_COUPLING, // U32 -- external trigger coupling id
		ETR_TTL      // U32 -- external trigger range id
	));

	ERROR_CHECK(AlazarSetTriggerDelay(m_boardHandle, 0));

	ERROR_CHECK(AlazarSetTriggerTimeOut(
		m_boardHandle, // HANDLE -- board handle
		0            // U32 -- timeout_sec / 10.e-6 (0 means wait forever)
	));

	ERROR_CHECK(AlazarConfigureAuxIO(m_boardHandle,     // HANDLE -- board handle
		AUX_IN_TRIGGER_ENABLE, // U32 -- mode
		TRIGGER_SLOPE_POSITIVE               // U32 -- parameter
	));//帧同步信号

	//测试
	//ERROR_CHECK(AlazarConfigureAuxIO(m_boardHandle,     // HANDLE -- board handle
	//	AUX_OUT_TRIGGER, // U32 -- mode
	//	0                // U32 -- parameter
	//));

	return TRUE;

exit:
	return success;
}

unsigned __stdcall AcquirePro::CreateSignal(void* param)
{
	AcquirePro *create_signal = (AcquirePro *)param;
	create_signal->createSignal();
	return 0;
}

void AcquirePro::createSignal()
{
	int m_iferror = pciStart(mpproc->clp.line_num, mpproc->clp.frame_num, mpproc->cp.phase, mpproc->clp.rep_num, mpproc->cp.sccale_x, mpproc->cp.scale_y, isshow);
	if (m_iferror < 0)
	{
		emit error("信号发生失败");
	}
}

unsigned __stdcall AcquirePro::SaveData(void* param)
{
	AcquirePro *save_data = (AcquirePro *)param;

	save_data->saveData();

	return 0;
}

void AcquirePro::saveData()
{
	int batchid = 0;
	while (listSaveBuffer.size() > 0 || isAcquire)
	{
		while (listSaveBuffer.size() > 0)
		{
			emit info("保存数据中...");

			std::string fullName = mpproc->cp.save_path + std::to_string((batchid) % mpproc->clp.frame_num + 1) + ".bin";
			std::ofstream gpuFile(fullName.c_str(), std::ios::binary);
			if (gpuFile.is_open())
			{
				unsigned short*send_address = listSaveBuffer.front();
				listSaveBuffer.pop_front();
				gpuFile.write((char *)send_address, sizeof(unsigned short) * mpproc->cp.A_line_pixal*mpproc->clp.line_num * mpproc->clp.rep_num);
				gpuFile.close();
				delete[] send_address;
				batchid++;
			}
			//Sleep(200);


		}

		Sleep(100);
	}

	emit info("保存完毕");

}

void AcquirePro::start_show()
{
	if (!isAcquire)
	{
		if (!configureBoard())
			return;
		acquireData();
	}
}

void AcquirePro::stop_show()
{
	isAcquire = false;
}

void AcquirePro::slot_show(bool _isshow)
{
	isshow = _isshow;
	start_show();
}


